package main

import (
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

var (
	db1 *gorm.DB
	db2 *gorm.DB
)

func main() {
	if len(os.Args) < 3 {
		fmt.Println(`当前库（用户名:密码@tcp(IP:端口)/数据库） 目标库（用户名:密码@tcp(IP:端口)/数据库） 表名(可选)`)
		return
	}
	var err error
	db1, err = gorm.Open(mysql.Open(os.Args[1]+"?charset=utf8mb4&parseTime=True&loc=Local"), &gorm.Config{})
	if err != nil {
		panic(err)
	}
	db2, err = gorm.Open(mysql.Open(os.Args[2]+"?charset=utf8mb4&parseTime=True&loc=Local"), &gorm.Config{})
	if err != nil {
		panic(err)
	}

	var data1 = make([]string, 0)
	var data2 = make([]string, 0)
	if len(os.Args) != 4 {
		data1 = getTables(db1)
		data2 = getTables(db2)
	} else {
		data1 = append(data1, os.Args[3])
		data2 = append(data2, os.Args[3])
	}

	if len(data1) == 0 {
		return
	}
	fmt.Println(data1)
	for _, table1 := range data1 {
		for _, table2 := range data2 {
			if table1 != table2 {
				continue
			}
			compare(1, db1, db2, table1)
		}
	}
}

func compare(action int, db1, db2 *gorm.DB, tableName string) {
	sql := fmt.Sprintf("SELECT * FROM %s", tableName)
	data := make([]map[string]interface{}, 0)
	err := db1.Raw(sql).Scan(&data).Error
	if err != nil {
		log.Printf("查询数据错误%s; 错误信息：%s", tableName, err.Error())
	}
	var insertFile *os.File
	var updateFile *os.File
	if action == 1 {
		os.Mkdir("data", 0666)
		insertFile, _ = os.Create("data/" + tableName + "-insert.sql")
		updateFile, _ = os.Create("data/" + tableName + "-update.sql")
	}
	deleteFile, _ := os.Create("data/" + tableName + "-delete.sql")
	defer insertFile.Close()
	defer updateFile.Close()
	defer deleteFile.Close()
	for _, row := range data {
		id := row["id"]
		_, value := getValue(id)
		sql := fmt.Sprintf("SELECT count(1) FROM %s where id = '%s'", tableName, value)
		count := 0
		err := db2.Raw(sql).Scan(&count).Error
		if err != nil {
			log.Printf("查询数据错误%s; 错误信息：%s\n", tableName, err.Error())
			continue
		}
		if count == 0 {
			if action == 1 {
				//插入数据
				names := make([]string, 0)
				values := make([]string, 0)
				for k, v := range row {
					names = append(names, k)
					t, value := getValue(v)
					if t == 1 {
						values = append(values, fmt.Sprintf("'%s'", value))
					} else {
						values = append(values, fmt.Sprintf("%s", value))
					}

				}
				sql := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s);\n", tableName, strings.Join(names, ", "), strings.Join(values, ", "))
				insertFile.WriteString(sql)
				fmt.Print(sql)
			} else if action == 2 {
				_, value := getValue(row["id"])
				sql := fmt.Sprintf("DELETE FROM %s WHERE id = '%s';\n", tableName, value)
				deleteFile.WriteString(sql)
				fmt.Print(sql)

			}
		} else if count == 1 && action == 1 {
			//修改数据
			idVal := row["id"]
			_, value := getValue(idVal)
			sql = "select * from " + tableName + " where id = '" + value + "'"
			id := fmt.Sprintf("id = '%s'", value)
			dbRow := make(map[string]interface{}, 0)
			err := db2.Raw(sql).Scan(&dbRow).Error
			if err != nil {
				log.Printf("查询数据错误:%s,数据ID:%s; 错误信息：%s\n", tableName, id, err.Error())
				continue
			}
			namesVals := make([]string, 0)
			for k, v := range row {
				if k == "id" {
					continue
				}
				if v != dbRow[k] {
					t, value := getValue(v)
					if t == 1 {
						namesVals = append(namesVals, fmt.Sprintf("%s = '%s'", k, value))
					} else {
						namesVals = append(namesVals, fmt.Sprintf("%s = %s", k, value))
					}
				}
			}
			if len(namesVals) == 0 {
				continue
			}
			sql := fmt.Sprintf("UPDATE %s SET %s WHERE %s;\n", tableName, strings.Join(namesVals, ", "), id)
			updateFile.WriteString(sql)
			fmt.Print(sql)
		}
	}
	if action == 1 {
		compare(2, db2, db1, tableName)
	}
}

func getTables(db *gorm.DB) []string {
	data := make([]string, 0)
	err := db.Raw("SELECT table_name as name  FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = (SELECT DATABASE())").Scan(&data).Error
	if err != nil {
		panic(err)
	}
	return data
}

func getValue(val interface{}) (int, string) {
	if val == nil {
		return 2, "null"
	}
	if v, ok := val.(string); ok {
		return 1, v
	} else if v, ok := val.(int); ok {
		return 2, fmt.Sprint(v)
	} else if v, ok := val.(int64); ok {
		return 2, fmt.Sprint(v)
	} else if v, ok := val.(float32); ok {
		return 2, fmt.Sprint(v)
	} else if v, ok := val.(float64); ok {
		return 2, fmt.Sprint(v)
	} else if v, ok := val.(time.Time); ok {
		return 1, v.Format("2006-01-02 15:04:05")
	} else {
		return 1, fmt.Sprint(v)
	}
}
